home *** CD-ROM | disk | FTP | other *** search
/ Complete Linux / Complete Linux.iso / docs / apps / database / postgres / postgre4.z / postgre4 / src / rules / prs2 / prs2putlocks.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-08-27  |  19.5 KB  |  643 lines

  1. /*======================================================================
  2.  *
  3.  * FILE:
  4.  *  prs2putlocks.c
  5.  *
  6.  * $Header: /private/postgres/src/rules/prs2/RCS/prs2putlocks.c,v 1.10 1991/11/18 22:21:22 mer Exp $
  7.  *
  8.  * DESCRIPTION:
  9.  *
  10.  * Take a deeeeeeep breath & read. If you can avoid hacking the code
  11.  * below (i.e. if you have not been "volunteered" by the boss to do this
  12.  * dirty job) avoid it at all costs. Try to do something less dangerous
  13.  * for your (mental) health. Go home and watch horror movies on TV.
  14.  * Read some Lovecraft. Join the Army. Go and spend a few nights in
  15.  * people's park. Commit suicide...
  16.  * Hm, you keep reading, eh? Oh, well, then you deserve what you get.
  17.  * Welcome to the gloomy labyrinth of the tuple level rule system, my
  18.  * poor hacker...
  19.  *
  20.  * This code is responsible for putting the rule locks and rule stubs.
  21.  * for tuple-level-system rules.
  22.  * For the time being we support the following implementations:
  23.  *
  24.  * ------------------------
  25.  * A) Relation Level Locks:
  26.  * ------------------------
  27.  * All we do is to put a "relation level lock",
  28.  * i.e. a lock to the appropriate pg_relation tuple. No rule stubs involved.
  29.  * ALL the tuples of the relation are assumed locked by this lock.
  30.  * This is the easiest implementation & it is quaranteed to work,
  31.  * iff we use it for ALL rules we define.
  32.  * If we use it for some rules only and for the others we use tuple level
  33.  * locks, then read the discussion below....
  34.  *
  35.  * -----------------------------
  36.  * B) (simple) Tuple Level Locks
  37.  * -----------------------------
  38.  * We start with the original rule qualification, and we extract a
  39.  * "constant" qualification. This qualifications must satisfy
  40.  * the following criteria:
  41.  *    1) all tuples satisfying the original qualiifcation will
  42.  *    satisfy the "constant" qual too (altohgou the opposite
  43.  *    might not be true in general)
  44.  *    2) the constan qualification will only reference attributes
  45.  *     of the CURRENT (locked) relation, i.e. it won't have any joins.
  46.  * For example if the rule's qual is :
  47.  *    where CURRENT.name = Mike 
  48.  *    and CURRENT.dept = DEPT.dname
  49.  *    and DEPT.floor = 1
  50.  *    and CURRENT.age > 30
  51.  *
  52.  * the constant qual would be:
  53.  *    where CURRENT.name = Mike and CURRENT.age>30
  54.  *
  55.  * We then add locks to all the tuples that satisfy the constant
  56.  * qualification. We also add a stub record (which contains this
  57.  * constant qualification, the rule locks and other misc info).
  58.  * Every time we append/replace a tuple, we check one by one all the
  59.  * stub records. If the new tuple satisfies the qualification of
  60.  * such a stub record, we add the appropriate locks to it.
  61.  * 
  62.  * Simple, isn't it? Well, as the philosopher said, "if everything
  63.  * seems to go right, then you have overlooked something".
  64.  * Assume the rules:
  65.  * 
  66.  *        RULE 1:
  67.  *        on retrieve to EMP.salary
  68.  *        where current.name = "spyros"
  69.  *        do instead retrieve (salary=1)
  70.  *
  71.  *        RULE 2:
  72.  *        on retrieve to EMP.desk
  73.  *        where current.salary = 1
  74.  *        do instead retrieve (desk = "what desk?")
  75.  *
  76.  * Lets say that we have a tuple:
  77.  *   [ NAME    SALARY    DESK  ]
  78.  *   [ spyros    2    steel ]
  79.  * Well, if we define RULE_1 first and RULE_2 second, everything is
  80.  * OK. However, if we define RULE_2 first, then we will NOT put
  81.  * a lock to spyros' tuple, because his salary is 2.
  82.  * So, when later on we define RULE_1 we must put in spyros' tuple not
  83.  * only the locks of RULE_1, but also the locks for RULE_2!
  84.  *
  85.  * To make things even worse, lets rewrite rule 1:
  86.  * RULE 1:    on retrieve to EMP.salary
  87.  *        where current.name = "spyros"
  88.  *        do instead retrieve (E.salary) where E.name = "mike"
  89.  * And lets say that initially mike has a salary equal to 2.
  90.  * Now even if we define RULE_1 first and RULE_2 second, still
  91.  * spyros' tuple will not get any locks. If later on we change mike's
  92.  * salary from 2 to 1, then we are in trouble....
  93.  *
  94.  * So, to cut a long story longer:
  95.  * a) we have to lock all the tuples that satisfy the constant qual OR
  96.  *    have a "write" lock in any attribute referenced by this qual.
  97.  *    Note that if such a "write" lock is a "relation level lock" (i.e
  98.  *    it -implicitly- locks ALL the tuples of the relation), then we
  99.  *    have to lock all the tuples of the relation too, so it
  100.  *    would be better if we use a relation level lock too.
  101.  * b) when we put a lock to a tuple, then if this lock is a "write"
  102.  *    lock (i.e. if our new rule is something like "on retrieve ... do
  103.  *    retrieve" we have also to check all the relation level stub records.
  104.  *    For every stub if its qualification references the attribute
  105.  *    we are currently putting this "write" lock on, then we have also to
  106.  *    add to this tuple the stub's lock... And of course if this stub's
  107.  *    lock is a "write" lock too, we have to repeat the process, until
  108.  *    there are no more new "write" locks added.
  109.  *
  110.  * OK, you say, yes things do look bad, but anyway, we will survive...
  111.  * Take a 5 minute (or hour or day or -even better- year) break, relax,
  112.  * think about the good things in life (if any - remember you are just
  113.  * a graduate student) and when you feel ready start reading again....
  114.  *
  115.  * If we have the rules RULE_1 & RULE_2 defined above, and RULE_1
  116.  * was for some reason implemented with a relation level lock, then
  117.  * all the tuples of EMP have a lock on salary, so RULE_2 would
  118.  * end up locking all the tuples too, i.e. it would be better
  119.  * to implement it with a relation level lock too.
  120.  * If we define first RULE_2 (using tuple level locks) and then
  121.  * we define RULE_1, then we have to delete RULE_1 (and all other rules
  122.  * that use EMP.salary) and redefine it using relation level locks.
  123.  *
  124.  * OK, I said whatever I had to say. But before you say something
  125.  * stupid like "well, at least it's over", remember that it took me
  126.  * sometime to discover the problems above. Who know how many other
  127.  * problems remain to be discovered too...
  128.  *
  129.  *=====================================================================
  130.  */
  131. #include "tmp/postgres.h"
  132. #include "nodes/pg_lisp.h"
  133. #include "utils/log.h"
  134. #include "rules/prs2.h"
  135. #include "rules/prs2stub.h"
  136. #include "nodes/primnodes.h"
  137. #include "nodes/primnodes.a.h"
  138. #include "planner/clause.h"
  139. #include "planner/clauses.h"
  140. #include "utils/lsyscache.h"
  141.  
  142. /*------------------------------------------------------------------
  143.  * prs2AddTheNewRule
  144.  *
  145.  * put all the apropriate rule locks/stubs & update the system relations.
  146.  *
  147.  * First try to implement this rule as a tuple-level-lock rule,
  148.  * and if that fails, try a relation-level-lock.
  149.  *
  150.  *------------------------------------------------------------------
  151.  */
  152. void
  153. prs2AddTheNewRule(r, hint)
  154. Prs2RuleData r;
  155. List hint;
  156. {
  157.     Prs2LockType lockType;
  158.     AttributeNumber attributeNo;
  159.     bool status;
  160.     bool hintFlag;
  161.  
  162.     switch (r->eventType) {
  163.     case EventTypeRetrieve:
  164.     case EventTypeDelete:
  165.     case EventTypeReplace:
  166.     case EventTypeAppend:
  167.         if (!null(hint) && LISPVALUE_INTEGER(hint) == RELATION) {
  168.         /*
  169.          * use relation level locks
  170.          */
  171.         prs2DefRelationLevelLockRule(r);
  172.         } else {
  173.         /*
  174.          * first check if we can use tuple level locking.
  175.          * If no, then use relation level locks...
  176.          * Unless we explicitly specify that we want
  177.          * to use tuple-level locks (in which case 'hintFlag'
  178.          * must be set to true.
  179.          */
  180.         if (!null(hint) && LISPVALUE_INTEGER(hint) == P_TUPLE)
  181.             hintFlag = true;
  182.         else
  183.             hintFlag = false;
  184.         status = prs2DefTupleLevelLockRule(r, hintFlag);
  185.         if (!status) {
  186.             prs2DefRelationLevelLockRule(r);
  187.         }
  188.         }
  189.         break;
  190.     default:
  191.         elog(WARN,
  192.         "prs2TupleSystemPutLocks: Illegal event type %c",
  193.         r->eventType);
  194.     }
  195. }
  196.  
  197. /*------------------------------------------------------------------
  198.  * prs2DeleteTheOldRule
  199.  *
  200.  * delete a rule given its oid...
  201.  *------------------------------------------------------------------
  202.  */
  203. void prs2DeleteTheOldRule(ruleId)
  204. ObjectId ruleId;
  205. {
  206.  
  207.     ObjectId relationId;
  208.  
  209.     /*
  210.      * find the relation locked by the rule
  211.      */
  212.     relationId = get_eventrelid(ruleId);
  213.     if (relationId == InvalidObjectId) {
  214.     /*
  215.      * This should not happen!
  216.      */
  217.     elog(WARN, "Can not find 'event' relation for rule %d", ruleId);
  218.     }
  219.  
  220.     /*
  221.      * Is it a tuple level lock rule ?
  222.      */
  223.     if (prs2IsATupleLevelLockRule(ruleId, relationId)) {
  224.     prs2UndefTupleLevelLockRule(ruleId, relationId);
  225.     } else {
  226.     prs2UndefRelationLevelLockRule(ruleId, relationId);
  227.     }
  228. }
  229.  
  230. /*------------------------------------------------------------------
  231.  *
  232.  * prs2FindLockTypeAndAttrNo
  233.  *
  234.  * Given the event & action types, the attribute number that
  235.  * appears in the event clause of the rule and the attribute number
  236.  * that is updated -if this is a rule of the form
  237.  *     "on... where... do replace CURRENT(...)"
  238.  * or
  239.  *     "on... where... do instead retrieve(...)"
  240.  *
  241.  * then find the type of the 'action' lock that we must put
  242.  * in the relation that appears in the event clause of the rule.
  243.  * and the attribute number of the attribute that must be locked.
  244.  *------------------------------------------------------------------
  245.  */
  246. void
  247. prs2FindLockTypeAndAttrNo(r, lockTypeResult, attributeNoResult)
  248. Prs2RuleData r;
  249. Prs2LockType *lockTypeResult;
  250. AttributeNumber *attributeNoResult;
  251. {
  252.     Prs2LockType lockType;
  253.     AttributeNumber attributeNo;
  254.  
  255.     lockType = LockTypeInvalid;
  256.     attributeNo = InvalidAttributeNumber;
  257.  
  258.     if (r->actionType == ActionTypeRetrieveValue &&
  259.     r->eventAttributeNumber == InvalidAttributeNumber) {
  260.     /*
  261.      * this is a "view" rule....
  262.      */
  263.     lockType = LockTypeRetrieveRelation;
  264.     attributeNo = InvalidAttributeNumber;
  265.     } else if (r->actionType == ActionTypeRetrieveValue ||
  266.     r->actionType == ActionTypeReplaceCurrent ||
  267.     r->actionType == ActionTypeReplaceNew) {
  268.     /*
  269.      * In this case the attribute to be locked is the updated
  270.      * attribute...
  271.      */
  272.     attributeNo = r->updatedAttributeNumber;
  273.     switch (r->eventType) {
  274.         case EventTypeRetrieve:
  275.         lockType = LockTypeRetrieveWrite;
  276.         break;
  277.         case EventTypeReplace:
  278.         lockType = LockTypeReplaceWrite;
  279.         if (r->actionType == ActionTypeReplaceCurrent) {
  280.             elog(WARN, "ON REPLACE rules can not update CURRENT tuple");
  281.         }
  282.         break;
  283.         case EventTypeAppend:
  284.         lockType = LockTypeAppendWrite;
  285.         if (r->actionType == ActionTypeReplaceCurrent) {
  286.             elog(WARN, "ON APPEND rules can not update CURRENT tuple");
  287.         }
  288.         break;
  289.         case EventTypeDelete:
  290.         lockType = LockTypeDeleteWrite;
  291.         elog(WARN, "ON DELETE rules can not update CURRENT tuple");
  292.         break;
  293.         default:
  294.         elog(WARN,
  295.             "prs2FindLockType: Illegal Event type: %c", r->eventType);
  296.     }
  297.     } else if (r->actionType == ActionTypeOther) {
  298.     /*
  299.      * In this case the attribute to be locked is the 'event'
  300.      * attribute...
  301.      */
  302.     attributeNo = r->eventAttributeNumber;
  303.     switch (r->eventType) {
  304.         case EventTypeRetrieve:
  305.         lockType = LockTypeRetrieveAction;
  306.         break;
  307.         case EventTypeReplace:
  308.         lockType = LockTypeReplaceAction;
  309.         break;
  310.         case EventTypeAppend:
  311.         lockType = LockTypeAppendAction;
  312.         break;
  313.         case EventTypeDelete:
  314.         lockType = LockTypeDeleteAction;
  315.         break;
  316.         default:
  317.         elog(WARN,
  318.             "prs2FindLockType: Illegal Event type: %c", r->eventType);
  319.     }
  320.     } else {
  321.     elog(WARN, "prs2FindLockType: Illegal Action type: %c", r->actionType);
  322.     }
  323.  
  324.     /*
  325.      * OK, we found them! update the output attributes.
  326.      */
  327.     *lockTypeResult = lockType;
  328.     *attributeNoResult = attributeNo;
  329.  
  330. }
  331.  
  332. /*------------------------------------------------------------------
  333.  *
  334.  * prs2FindConstantQual
  335.  *
  336.  * This routine is given the rule's qualification and returns
  337.  * a "more relaxed" qualification with the following properties:
  338.  *
  339.  * 1) every tuple that satisfies the original qualification
  340.  *    will satisfy the returned qualification (while of course the
  341.  *    opposite is not true in general).
  342.  * 2) this retruned qualification will only involve constants and
  343.  *    .e. attributes of the CURRENT (the one to be
  344.  *    locked). It will NOT have any 'Var' nodes of other tuple variables.
  345.  *
  346.  * NOTE 1: the "Var" nodes of the NEW/CURRENT relation are NOT
  347.  * replaced with "Param" nodes.
  348.  * NOTE 2: We are only interested in the CURRENT relation. We ignore
  349.  * the NEW relation (we treat is as another tuple variable)!
  350.  *
  351.  * This routine can also return a null qualification, which is
  352.  * assumed to be equivalent to 'always true'.
  353.  *
  354.  * For example if the original qual is:
  355.  *
  356.  *    where CURRENT.dept = DEPT.dname and DEPT.floor = 1
  357.  *    and CURRENT.age > 30 and CURRENT.salary > 5000
  358.  *    and CURRENT.salary > EMP.salary and EMP.name = CURRENT.mgr
  359.  *
  360.  * the returned qualification will be:
  361.  *    where CURRENT.age > 30 and CURRENT.salary > 5000
  362.  *
  363.  * If the original qual is:
  364.  *
  365.  *    where CURRENT.dept = DEPT.dname and DEPT.floor = 1
  366.  *
  367.  * then the returned qualification will be null (LispNil).
  368.  *
  369.  * NOTE: 
  370.  * this routine is not always doing the best possible job, i.e. it might
  371.  * not return the most restrictive qual that satisfies the 2 criteria
  372.  * mentioned before. Actually, if it gets confused it might just give up
  373.  * and return a null qual...
  374.  *
  375.  * NOTE 2:
  376.  * 'currentVarno' is the "varno" of the "Var" nodes corresponding
  377.  * to the CURRENT relation (currently this is always 1).
  378.  * 
  379.  *------------------------------------------------------------------
  380.  */
  381. LispValue
  382. prs2FindConstantQual(qual, currentVarno)
  383. LispValue qual;
  384. int currentVarno;
  385. {
  386.     LispValue newQual;
  387.     LispValue cnfify();
  388.     bool isConstant;
  389.  
  390.     /*
  391.      * use the 'cnfify' routine defined somewhere in the
  392.      * planner to convert the qual to Conjuctive Normal Form
  393.      * (tell 'cnfify' to NOT remove the explicit 'AND's !)
  394.      *
  395.      * NOTE: I am not sure whether we need to make a copy of
  396.      * the qual or not, but better be safe than sorry...
  397.      */ 
  398.     newQual = cnfify(lispCopy(qual), false);
  399.  
  400.     /*
  401.      * Hacky stuff!
  402.      * If the qual is a very simple one, i.e. "(<operator> <arg1> <arg2>)"
  403.      * then the parser does NOT enclose it into an extra set of
  404.      * parentheses and does NOT add the 'AND' operator.
  405.      */
  406.  
  407.     /*
  408.      * Now newQual is in CNF, i.e. it has a big 'AND'
  409.      * clause and all its elements are OR clauses of
  410.      * operators or maybe negations of operators.
  411.      *
  412.      * NOTE: there are also a couple of trivial cases:
  413.      * a) the qual is NIL
  414.      * b) the qual has only one clause and no explicit AND
  415.      */
  416.     newQual = prs2FindConstantClause(newQual, currentVarno, &isConstant);
  417.  
  418.     return(newQual);
  419.     
  420. }
  421.  
  422. /*------------------------------------------------------------------
  423.  *
  424.  * prs2FindConstantClause
  425.  *
  426.  * Traverse recursively a clause (AND, OR, NOT, Oper etc.)
  427.  * and extract the "relaxed & constant" version of it, i.e. a
  428.  * clause that has only Const nodes & attributes of the CURRENT relation
  429.  * as operands and it is more general than the original query (i.e. every
  430.  * tuple that satisfies the orignal clause will satisfy the new one).
  431.  *
  432.  * The '*isConstant' is true if the returned clause is the same
  433.  * as the original, i.e. the original was already a "constant"
  434.  * qual.
  435.  *------------------------------------------------------------------
  436.  */
  437. LispValue
  438. prs2FindConstantClause(clause, currentVarno, isConstant)
  439. LispValue clause;
  440. int currentVarno;
  441. bool *isConstant;
  442. {
  443.     LispValue t;
  444.     LispValue newSubclause;
  445.     LispValue newAndClauses, newOrClauses;
  446.     bool subclauseIsConst;
  447.  
  448.     if (null(clause)) {
  449.     *isConstant = true;
  450.     return(LispNil);
  451.     } else if (IsA(clause,Var)) {
  452.     /*
  453.      * Is it a CURRENT Var node ?
  454.      */
  455.     if (get_varno((Var)clause) == currentVarno) {
  456.         *isConstant = true;
  457.         return(clause);
  458.     } else {
  459.         /*
  460.          * no it is a var node of another relation...
  461.          * tough luck!
  462.          */
  463.         *isConstant = false;
  464.         return(LispNil);
  465.     }
  466.     } else if (IsA(clause,Const)) {
  467.     /*
  468.      * good! It is a constant....
  469.      */
  470.     *isConstant = true;
  471.     return(clause);
  472.     } else if (is_clause(clause)) {
  473.     /*
  474.      * it's an operator.
  475.      * Check if the operands are constants too...
  476.      */
  477.     foreach(t, get_opargs(clause)) {
  478.         newSubclause = prs2FindConstantClause(CAR(t),
  479.                     currentVarno,
  480.                     &subclauseIsConst);
  481.         if (!subclauseIsConst) {
  482.         /*
  483.          * at least one operand is not constant.
  484.          * That condemns the whole operator clause!
  485.          */
  486.         *isConstant = false;
  487.         return(LispNil);
  488.         }
  489.     }
  490.     /*
  491.      * all operands have passed the test!
  492.      * Return the clause as it is.
  493.      */
  494.     *isConstant = true;
  495.     return(clause);
  496.     } else if (is_funcclause(clause)) {
  497.     /*
  498.      * it's a function
  499.      * Check if its arguments are constants too...
  500.      */
  501.     foreach(t, get_funcargs(clause)) {
  502.         newSubclause = prs2FindConstantClause(CAR(t),
  503.                     currentVarno,
  504.                     &subclauseIsConst);
  505.         if (!subclauseIsConst) {
  506.         /*
  507.          * at least one operand is not constant.
  508.          * That condemns the whole operator clause!
  509.          */
  510.         *isConstant = false;
  511.         return(LispNil);
  512.         }
  513.     }
  514.     /*
  515.      * all arguments have passed the test!
  516.      * Return the clause as it is.
  517.      */
  518.     *isConstant = true;
  519.     return(clause);
  520.     } else if (and_clause(clause)) {
  521.     /*
  522.      * It's an AND clause.
  523.      * Apply the algorithm recursivelly to its
  524.      * subclauses, and return the conjuction of the results.
  525.      */
  526.     *isConstant = true;
  527.     newAndClauses = LispNil;
  528.     foreach (t, get_andclauseargs(clause)) {
  529.         newSubclause = prs2FindConstantClause(CAR(t), 
  530.                     currentVarno,
  531.                     &subclauseIsConst);
  532.         *isConstant = *isConstant && subclauseIsConst;
  533.         if (newSubclause != LispNil)
  534.         newAndClauses = nappend1(newAndClauses, newSubclause);
  535.     }
  536.     if (newAndClauses == LispNil) {
  537.         /*
  538.          * bad luck! We couldn't find a "relaxed" constant
  539.          * qualification...
  540.          */
  541.         return(LispNil);
  542.     } else {
  543.         /*
  544.          * make an AND clause
  545.          */
  546.         return(make_andclause(newAndClauses));
  547.     }
  548.     } else if (or_clause(clause)) {
  549.     /*
  550.      * It's an OR clause. Apply the algorithm recursively
  551.      * to all its subclauses. If any one of them returns
  552.      * LispNil though (i.e. can not be reduced to a more "relaxed"
  553.      * constant clause) then we have to return LispNil too!
  554.      */
  555.     *isConstant= true;
  556.     newOrClauses = LispNil;
  557.     foreach (t, get_orclauseargs(clause)) {
  558.         newSubclause = prs2FindConstantClause(CAR(t),
  559.                     currentVarno,
  560.                     &subclauseIsConst);
  561.         *isConstant = *isConstant && subclauseIsConst;
  562.         if (newSubclause == LispNil) {
  563.         return(LispNil);
  564.         } else {
  565.         newOrClauses = nappend1(newOrClauses, newSubclause);
  566.         }
  567.     }
  568.     if (newOrClauses == LispNil) {
  569.         /*
  570.          * Hmm! this can only happen if we don't have
  571.          * any arguments inthe original OR clause...
  572.          * In any case, lets return LispNil..
  573.          */
  574.         return(LispNil);
  575.     } else {
  576.         /*
  577.          * make an OR clause
  578.          */
  579.         return(make_orclause(newOrClauses));
  580.     }
  581.     } else if (not_clause(clause)) {
  582.     /*
  583.      * It's a NOT clause.
  584.      * If the argument of not is a constant qual then
  585.      * return LispNil (there is nothing we can do about it..)
  586.      */
  587.     newSubclause = prs2FindConstantClause(
  588.                 get_notclausearg(clause),
  589.                 currentVarno,
  590.                 &subclauseIsConst);
  591.     if (newSubclause == LispNil || subclauseIsConst) {
  592.         *isConstant = true;
  593.         return(LispNil);
  594.     } else {
  595.         *isConstant = false;
  596.         return(make_notclause(newSubclause));
  597.     }
  598.     } else {
  599.     /*
  600.      * well it is something we do not know how to handle...
  601.      * Play it cool & ignore it...
  602.      */
  603.     *isConstant = false;
  604.     return(LispNil);
  605.     }
  606. }
  607.  
  608. /*----------------------------------------------------------------------
  609.  * prs2IsATupleLevelLockRule
  610.  *
  611.  * return true if this is a tuple-level-lock rule.
  612.  * Check the stubs of the relation and if you find a stub for this rule
  613.  * there, then it is a tuple-level-lock rule, otherwise not!
  614.  *
  615.  *----------------------------------------------------------------------
  616.  */
  617. bool
  618. prs2IsATupleLevelLockRule(ruleId, relationId)
  619. ObjectId ruleId;
  620. ObjectId relationId;
  621. {
  622.     Prs2Stub stubs;
  623.     int i;
  624.     bool res;
  625.  
  626.     stubs = prs2GetRelationStubs(relationId);
  627.  
  628.     res = false;
  629.  
  630.     for (i=0; i<stubs->numOfStubs; i++) {
  631.     if (stubs->stubRecords[i]->ruleId == ruleId) {
  632.         res = true;
  633.         break;
  634.     }
  635.     }
  636.  
  637.     prs2FreeStub(stubs);
  638.  
  639.     return(res);
  640. }
  641.  
  642.  
  643.